<template>
  <div class="box-root" ref="boxRoot"
       v-loading="cpIsLoading"
       element-loading-text="加载中"
       element-loading-spinner="spinner-user"
       element-loading-background="rgba(0, 0, 0, 0)"
       element-loading-custom-class="spinner-custom"
  >
    <div class="custom-tree-search">
      <t-input size="small" v-model="searchKey" @enter="_searchKeyChange"  :disabled="cpIsLoading" placeholder="回车搜索" type="search" class="input-left">
        <template #suffixIcon>
          <icon name="search" />
        </template>
      </t-input>

      <customSpace  class="input-right">
        <template #content>
          <customButton title="刷新" theme="primary" @click="_load" :disabled="cpIsLoading" size="small"/>
          <t-switch
            size="large"
            v-model="isExpand"
            :label="['展开', '折叠']"
            @change="_isExpandChange"
            :disabled="cpIsLoading"
          />
        </template>
      </customSpace>
    </div>
    <div class="custom-tree" :style="getStyleCustomTree()">
      <t-tree
        hover
        activable
        :expandAll="cpExpandAll"
        :data="treeData"
        :expanded="expandList"
        :expand-level="expandLevel"
        @click="_click"
        @change="_change"
        :filter="treeFilter"
        :defaultActived="defaultActived"
        :keys="keys"
        ref="treeInst"
        :draggable="draggable"
        @drag-start="_dragStart"
        @drag-end="_dragEnd"
        @drop="_dragDrop"
        allow-fold-node-on-filter
        :checkable="checkable"
        :checkStrictly="checkStrictly"
        v-model.sync="valueThis"
      >
        <template #icon="{ node }">
          <!--有子节点，收缩的状态-->
          <icon v-if="node.getChildren() && !node.expanded" name="caret-right" />

          <!--有子节点，展开的状态-->
          <icon v-else-if="node.getChildren() && node.expanded" name="caret-down" />

          <!--其他状态-->
          <icon v-else name="minus-rectangle" />
        </template>

        <!--右侧按钮-->
        <template #operations="{ node }" v-if="showNodeOp">
          <customSpace >
            <template #content>
              <customButton
                v-for="item in nodeOp"
                :key="item.key"
                :title="item.title"
                :help="item.help"
                :theme="item.theme"
                :size="item.size"
                @click="_nodeOp(item, node)"
                v-show="!['addChild', 'addBefore', 'addAfter'].includes(item.key) || !node.data.isNew"
              />
            </template>
          </customSpace>
        </template>

        <!--  右侧按钮插槽-->
        <template #operations="{ node }">
          <slot name="customOperations" :node="node">
          </slot>
        </template>

      </t-tree>
    </div>
  </div>
</template>

<script>
import { Icon } from 'tdesign-icons-vue'

export default {
  desc: '自定义树',
  name: 'customTree',
  components: {
    Icon,
  },
  props: {
    /**
     * @Description：树数据
     * @Date 2023-10-10
     */
    data: {
      type: Array,
      default: () => [],
    },
    /**
     * @Description：源数据
     * @Date 2023-10-22
     */
    dataAll: {
      type: Array,
      default: () => [],
    },

    /**
     * @Description：节点显示配置
     * @Date 2023-10-10
     */
    keys: {
      type: Object,
      default: () => {
        return { value: 'id', label: 'label', children: 'children' }
      },
    },

    /**
     * @Description：默认选中节点
     * @Date 2023-10-10
     * @param [节点value值]
     */
    defaultActived: {
      type: Array,
      default: () => {
        return [0]
      },
    },
    /**
     * @Description：树容器最大高度减去值
     * @Date 2023-10-10
     */
    subHeight: {
      type: Number,
      default: () => 160,
    },
    /**
     * @Description：是否加载中
     * @Date 2023-10-10
     */
    isLoading: {
      type: Boolean,
      default: () => false,
    },
    /**
     * @Description：是否显示树节点右侧操作按钮
     * @Date 2023-10-10
     */
    showNodeOp: {
      type: Boolean,
      default: () => false,
    },
    /**
     * @Description：默认展开所有节点
     * @Date 2023-10-10
     */
    expandAll: {
      type: Boolean,
      default: () => false,
    },
    /**
     * @Description：默认展开第几级节点
     * @Date 2023-10-10
     */
    expandLevel: {
      type: Number,
      default: () => 2,
    },

    /**
     * @Description：树节点右侧按钮
     * @Date 2023-10-10
     */
    nodeOp: {
      type: Array,
      default: () => {
        return [
          {key: 'addChild', title: '子', size: 'small', theme: 'primary', help: '增加子节点', isConfirm: true},
          {key: 'addBefore', title: '前', size: 'small', theme: 'primary', help: '增加前置节点', isConfirm: true},
          {key: 'addAfter', title: '后', size: 'small', theme: 'primary', help: '增加后置节点', isConfirm: true},
          {key: 'remove', title: '删', size: 'small', theme: 'danger', help: '删除该节点', isConfirm: true},
        ]
      },
    },

    /**
     * @Description：根节点值
     * @Date 2023-10-13
     */
    rootNodeValue: {
      type: [String, Number],
      default: () => 0,
    },

    /**
     * @Description：是否可以拖动
     * @Date 2023-10-10
     */
    draggable: {
      type: Boolean,
      default: () => false,
    },
    /**
     * @Description：显示复选框
     * @Date 2023-10-20
     */
    checkable: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    /**
     * @Description：严格模式 true 父子节点选中状态不再关联，可各自选中或取消
     * @Date 2023-10-20
     */
    checkStrictly: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    /**
     * @Description：所选中的节点列表
     * @Date 2023-10-20
     */
    propCheckIdList: {
      type: Array,
      default: () => {
        return []
      }
    },
    /**
     * @Description：true: 局部刷新，根据数据对节点增删改
     * @Date 2023-10-20
     */
    propPartLoad: {
      type: Boolean,
      default: () => {
        return false
      }
    }
  },
  created() {
    this.isExpand = this.expandAll
  },
  data() {
    return {
      // 搜索文字
      searchKey: '',

      // 树搜索
      treeFilter: null,

      // 是否展开
      isExpand: false,

      // 上一次操作的节点标志
      nodeValueLast: null,

      // 上一次操作的节点数据
      nodeDataLast: null,

      // 上一次操作的节点前一个同胞节点value null 不存在
      nodeBeforeValue: null,

      // 上一次操作的节点后一个同胞节点value null 不存在
      nodeAfterValue: null,
      // 所展开的节点
      expandList: [],
      // 是否加载中
      isInnerLoading: false,

      // 所选中的节点id
      checkedIdList: [],

      // 选中值，组件为可选状态时有效。支持语法糖 v-model
      valueThis: [],

      // 树数据
      treeData: [],
    }
  },
  methods: {
    // 节点点击时触发
    _click(context) {
      this.$emit('click', JSON.parse(JSON.stringify(context.node.data)))
    },
    // 复选框选中时触发
    _change(checked, context) {
      this.checkedIdList = checked
    },

    // 搜索改变
    _searchKeyChange() {
      if (this.isInnerLoading) {
        return
      }
      this.isInnerLoading = true
      setTimeout(() => {
        this._searchKeyExec()
      }, 500)
    },

    // 搜索改变
    _searchKeyExec() {
      if (this.searchKey) {
        this.treeFilter = (node) => {
          let nodeLabel = ('' + node.data[this.cpLabelKey])
          return nodeLabel.indexOf(this.searchKey) != -1
            || this.pyutils.getAllFirstLetter(nodeLabel).toLowerCase().indexOf(this.searchKey) != -1
            || this.pyutils.chineseToPinYin(nodeLabel).toLowerCase().indexOf(this.searchKey) != -1
        }
      } else {
        this.treeFilter = null
      }
      setTimeout(() => {
        this.isInnerLoading = false
      }, 500)
    },

    // 获取树容器样式
    getStyleCustomTree() {
      return {
        maxHeight: `calc(100vh - ${this.subHeight}px)`,
        height: `calc(100vh - ${this.subHeight}px)`
      }
    },

    // 加载数据
    _load() {
      if (this.isInnerLoading) {
        return
      }
      this.isInnerLoading = true
      setTimeout(() => {
        this._loadExec()
      }, 500)
    },

    // 加载数据
    _loadExec() {
      if (this.$parent && this.$parent.load) {
        this.$parent.load()
      }
      setTimeout(() => {
        this.isInnerLoading = false
      }, 500)
    },

    // 节点点击时触发
    getInst() {
      return this.$refs.treeInst
    },

    /*
     * @Description：获取树节点-根据节点标志
     * @Date 2023-10-12
     * @param value 节点标志
    */
    getNodeByValue(value) {
      return this.getInst().getItem(value)
    },

    /*
     * @Description：获取树节点标签-根据节点标志
     * @Date 2023-10-12
     * @param value 节点标志
    */
    getNodeLabelByValue(value) {
      let node = this.getNodeByValue(value)
      if (node) {
        return node.data[this.keys.label]
      }
      return ''
    },

    /*
     * @Description：获取所有选中的节点数据
     * @Date 2023-10-12
     * @param isAll true 也返回半选中的节点
    */
    getCheckDataList(isAll = false) {
      if (this.checkedIdList.length == 0) {
        return []
      }
      let nodeAll = this.getInst().getItems(null)
      if (nodeAll.length == 0) {
        return []
      }
      if (isAll) {
        return nodeAll.filter(node => node.checked || node.indeterminate).map(node2 => {
          return node2.data
        })
      }
      return nodeAll.filter(node => node.checked).map(node2 => {
        return node2.data
      })
    },

    /*
     * @Description：更新树节点
     * @Date 2023-10-12
     * @param value 节点标志
     * @param data  节点数据
     * @param isForce  true: 强制执行重新移除添加节点
    */
    nodeUpdate(value, data, isForce = false) {
      let node = this.getNodeByValue(value)
      if (node) {
        // 父节点没有变化
        if (!isForce && node.data.parentId == data.parentId) {
          node.setData(data)
          return false
        }

        node.remove()
        // 有前一个同胞节点，则插入到它后面
        if (this.nodeBeforeValue) {
          let subNode = this.getNodeByValue(this.nodeBeforeValue)
          if (!subNode) {
            this.utilMessage.warning('更新节点失败，同胞节点不存在')
            return false
          }
          this.getInst().insertAfter(subNode.value, data)
          return false
        }

        // 有后一个同胞节点，则插入到它前面
        if (this.nodeAfterValue) {
          let subNode = this.getNodeByValue(this.nodeAfterValue)
          if (!subNode) {
            this.utilMessage.warning('更新节点失败，同胞节点不存在')
            return false
          }
          this.getInst().insertBefore(subNode.value, data)
          return false
        }

        // 追加到父节点
        let parentNode = this.getNodeByValue(data.parentId)
        if (!parentNode) {
          this.utilMessage.warning('更新节点失败，父节点不存在')
          return false
        }
        this.getInst().appendTo(parentNode.value, data)
      }
    },

    /*
     * @Description：删除树节点
     * @Date 2023-10-12
     * @param value 节点标志
    */
    nodeRemove(value) {
      this.getInst().remove(value)
    },

    /*
     * @Description：恢复最后一次操作的节点
     * @Date 2023-10-12
     * @param value 节点标志
    */
    nodeRevertDataLast() {
      if (this.nodeValueLast) {
        this.nodeUpdate(this.nodeValueLast, this.nodeDataLast, true)
      }
    },

    /**
     * @Description：设置节点状态
     * @Date 2023-10-13
     * @param value 节点标志
     * @param status 需要设置的状态对象
     */
    _setNodeStatus(value, status) {
      this.getInst().setItem(value, status)
    },

    /**
     * @Description：设置节点状态
     * @Date 2023-10-13
     * @param value       节点标志
     * @param statusValue 当前节点是否处于高亮激活态 true 是
     */
    setNodeActived(value, statusValue) {
      this._setNodeStatus(value, {actived: statusValue})
    },

    /**
     * @Description：设置节点状态
     * @Date 2023-10-13
     * @param value       节点标志
     * @param statusValue 当前节点是否被选中 true 是
     */
    setNodeChecked(value, statusValue) {
      this._setNodeStatus(value, {checked: statusValue})
    },

    /**
     * @Description：设置节点状态
     * @Date 2023-10-23
     * @param value       节点标志数组
     * @param statusValue 当前节点是否被选中 true 是
     */
    setNodeListChecked(valueList, statusValue) {
      let keyValue = this.keys.value
      // 只选中叶子节点
      let nodeLeafAll = this.dataAll.filter(
        node => valueList.includes(node[keyValue])
          && this.dataAll.findIndex(tmp => tmp.parentId == node[keyValue]) == -1
      ).map((node2) => {
        return node2[keyValue]
      })
      this.valueThis = nodeLeafAll
      this.checkedIdList = valueList
    },

    /**
     * @Description：设置节点状态
     * @Date 2023-10-13
     * @param value       节点标志
     * @param statusValue 当前节点是否展开 true 是
     */
    setNodeExpanded(value, statusValue) {
      this._setNodeStatus(value, {expanded: statusValue})
    },

    // 展开状态变化
    _isExpandChange() {
      if (this.isInnerLoading) {
        return
      }
      this.isInnerLoading = true
      setTimeout(() => {
        this._isExpandExec()
      }, 500)
    },

    // 展开状态变化
    _isExpandExec() {
      let nodes = this.getInst().getItems().filter(tmp => tmp.expanded != this.isExpand)
      if (nodes.length == 0) {
        this.isInnerLoading = false
        return
      }
      for (let i in nodes) {
        this.setNodeExpanded(nodes[i].value, this.isExpand)
      }
      setTimeout(() => {
        this.isInnerLoading = false
      }, 500)
    },

    // 节点操作
    _nodeOp(btnConfig, node) {
      this.nodeValueLast = node.value
      this.nodeDataLast = JSON.parse(JSON.stringify(node.data))
      // 父组件前缀方法,一般做检验，返回false，将终止操作
      if (this.$parent && this.$parent.nodeOpBefore) {
        let ret = this.$parent.nodeOpBefore(btnConfig, node)
        if (ret != undefined && ret == false) {
          return
        }
      }
      if (btnConfig.key == 'remove') {
        if (node.getChildren().length > 0) {
          this.utilMessage.warning('该节点存在下级节点，不允许删除')
          return
        }
      }

      if (btnConfig.isConfirm) {
        this.utilMessage.confirm(`是否执行操作【${btnConfig.help}】`, () => {
          this._nodeOpExec(btnConfig, node)
        })
        return
      }
      this._nodeOpExec(btnConfig, node)
    },

    // 节点操作执行
    _nodeOpExec(btnConfig, node) {
      let itemNew = {id: this.utils.randomCode(8), isNew: true}
      let nodeActivedValue = null
      // eslint-disable-next-line default-case
      switch (btnConfig.key) {
        case 'addChild': { // 添加子节点
          itemNew[this.keys.label] = '子节点'
          itemNew.parentId = node.value
          this.getInst().appendTo(node.value, itemNew)
          nodeActivedValue = itemNew.id
          break
        }
        case 'addBefore': { // 前插节点
          itemNew[this.keys.label] = '前插节点'
          if (node.getParent()) {
            itemNew.parentId = node.getParent().value
            this.rootNodeValue
          } else {
            itemNew.parentId = this.rootNodeValue
          }
          this.getInst().insertBefore(node.value, itemNew)
          nodeActivedValue = itemNew.id
          break
        }
        case 'addAfter': { // 后插节点
          itemNew[this.keys.label] = '后插节点'
          if (node.getParent()) {
            itemNew.parentId = node.getParent().value
            this.rootNodeValue
          } else {
            itemNew.parentId = this.rootNodeValue
          }
          this.getInst().insertAfter(node.value, itemNew)
          nodeActivedValue = itemNew.id
          break
        }
        case 'remove': { // 删除
          itemNew = {}
          this.nodeRemove(node.value)
          break
        }
        case 'dragEnd': { // 拖动结束
          itemNew = node.data
          nodeActivedValue = itemNew.value
          break
        }
        case 'nodeRevertDataLast': { // 恢复最后一次操作的节点
          itemNew = this.nodeDataLast
          this.nodeRevertDataLast()
          nodeActivedValue = this.nodeValueLast
          break
        }
        default: { // 调用父组件方法
          if (this.$parent && this.$parent.nodeOp) {
            itemNew = {}
            this.$parent.nodeOp(btnConfig, node)
          }
        }
      }

      // 高亮节点
      if (nodeActivedValue != null) {
        this.setNodeActived(nodeActivedValue, true)
      }

      // 父组件后缀方法
      if (this.$parent && this.$parent.nodeOpAfter) {
        this.$parent.nodeOpAfter(btnConfig, node, itemNew)
      }
    },

    // 拖动开始
    _dragStart(ctx) {
      this.setNodeActived(ctx.node.value, true)
      this.nodeBeforeValue = null
      this.nodeAfterValue = null
      // 同胞节点，包含自己
      let sbNodes = ctx.node.getSiblings()
      let findIndex = sbNodes.findIndex(tmp => tmp.value == ctx.node.value)
      if (sbNodes.length == 1) { // 1个节点
        this.nodeBeforeValue = null
        this.nodeAfterValue = null
      } else if (findIndex - 1 == -1) { // 没有前置节点，即当前节点是第一个
        this.nodeBeforeValue = null
        this.nodeAfterValue = sbNodes[findIndex + 1].value
      } else if (findIndex == sbNodes.length - 1) { // 当前节点是最后一个节点
        this.nodeBeforeValue = sbNodes[findIndex - 1].value
        this.nodeAfterValue = null
      } else { // 中间节点
        this.nodeBeforeValue = sbNodes[findIndex - 1].value
        this.nodeAfterValue = null
      }
    },

    // 节点在目标元素上释放时触发
    _dragDrop(ctx) {
      this.nodeValueLast = ctx.dragNode.value
      this.nodeDataLast = JSON.parse(JSON.stringify(ctx.dragNode.data))
      // 放置到非根节点
      if (ctx.dropNode.getParent()) {
        ctx.dragNode.data.parentId = ctx.dragNode.getParent().value
      } else {
        ctx.dragNode.data.parentId = this.rootNodeValue
      }

      this.nodeUpdate(ctx.dragNode.value, ctx.dragNode.data)
    },

    // 拖动结束
    _dragEnd(ctx) {
      this.utilMessage.confirm(`是否保存节点【${ctx.node.label}】的移动操作`, () => {
        this._nodeOpExec({key: 'dragEnd'}, ctx.node)
      }, () => {
        this.nodeRevertDataLast()
      })
    },

    /***
     * @return:
     * @Author: Tai
     * @date: 2024/6/14
     * @Description:  获取某节点的全部子孙节点；参数为空，则表示获取整棵树的全部节点，泛型 T 表示树节点 TS 类型
     */
    getItems(param = null) {
      return this.getInst().getItems(param)
    },

    /***
     * @Description:  局部刷新节点
     */
    _uploadNodeListPart(data = []) {
      // 更新或者删除的节点
      let nodeList = this.getInst().getItems()
      if (nodeList && nodeList.length > 0) {
        nodeList.forEach(node => {
          let findData= this.utils.findTreeNode(data, r => r.id == node.value)
          if (findData && findData.length == 1) {  // 更新节点
            node.setData(findData[0])
          } else { // 移除节点
            node.remove()
          }
        })
      }

      // 新增节点
      let index = 0
      let nodeFind
      data.forEach(dataOne => {
        nodeFind = this.getInst().getItem(dataOne.id)
        if (!nodeFind) {
          if (index == 0) { // 追加到根
            this.getInst().appendTo(0,dataOne)
          } else{ //追加到前一个节点的后面
            this.getInst().insertAfter(data[index - 1].id,dataOne)
          }
        }
        if (dataOne.children && dataOne.children.length > 0) {
          this._uploadNodeListPartChildren(dataOne.id,dataOne.children,nodeList)
        }
        index++
      })
    },

    // 追加子节点
    _uploadNodeListPartChildren(parentId,childrenData,nodeList) {
      let index = 0
      let nodeFind
      childrenData.forEach(dataOne => {
        nodeFind = this.getInst().getItem(dataOne.id)
        if (!nodeFind) {
          if (index == 0) { // 追加到父节点
            this.getInst().appendTo(parentId,dataOne)
          } else{ //追加到前一个节点的后面
            this.getInst().insertAfter(childrenData[index - 1].id,dataOne)
          }
        }
        if (dataOne.children && dataOne.children.length > 0) {
          this._uploadNodeListPartChildren(dataOne.id,dataOne.children, nodeList)
        }
        index++
      })
    }
  },
  watch: {
    // 根据选中节点选中
    data: {
      immediate: true, // 初始创建 watch 时立即执行回调函数
      deep: true, // 监听到对象里面的值的变化
      handler(newValue) {
        // 局部刷新
        if (this.propPartLoad) {
          if (this.treeData.length == 0) {
            this.treeData = newValue
          } else {
            this._uploadNodeListPart(newValue)
          }
        } else {
          this.treeData = newValue
        }

        this.$nextTick(() => {
          this.setNodeListChecked(this.propCheckIdList, true)
        })
      },
    },
    // 根据选中节点选中
    propCheckIdList: {
      immediate: true, // 初始创建 watch 时立即执行回调函数
      deep: true, // 监听到对象里面的值的变化
      handler() {
        this.$nextTick(() => {
          this.setNodeListChecked(this.propCheckIdList, true)
        })
      },
    }
  },
  computed: {
    // 标签字段名
    cpLabelKey() {
      return this.keys.label
    },
    cpIsLoading() {
      return this.isLoading || this.isInnerLoading
    },
    cpExpandAll() {
      return this.expandAll || this.isExpand
    }
  },
}
</script>

<style lang="scss">
.box-root {
  .t-tree__item{
    cursor: pointer;
  }
  .t-tree__item:hover,.t-tree__item:has(.t-is-active) {
    background-color: #d5dffb !important;
    .t-tree__label{
      font-weight: bold;
      color: #1068f9;
    }
  }
  .t-tree__label,.t-tree__label:hover{
    background: transparent !important;
  }

  .custom-tree-search {
    margin-top: 2px;
  }

  .custom-tree{
    overflow: auto;
    background: #f5f7fa;
    margin-top: 14px;
  }

  .input-left{
    width: calc(100% - 110px) !important;
    padding-left: 10px;
  }
  .input-left,.input-right{
    display: inline-block !important;
  }
  .input-right .t-space-item{
    display: inline-block;
    margin-left: 5px;
  }

}
</style>
